package json;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.FileUtils;
import org.bson.Document;
import org.json.JSONArray;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import com.dslplatform.json.DslJson;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import io.avaje.jsonb.JsonType;
import io.avaje.jsonb.Jsonb;
import jodd.json.JsonParser;
import jodd.json.JsonSerializer;
import utils.MongoUtils;

/**
 * The Class JSONUtilsBenchmark.
 */
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Thread)
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class JSONUtilsBenchmark {
	
	public static final String BENCHMARK_COLLECTION = "benchmark";

	public static final String JSON_UTILS_DATABASE = "json_utils";

	/** The json file path. */
	private static String jsonFilePath = "/home/ralph/temp/zndb$SocialUser.json";
	
	/** The json file content. */
	private static String jsonFileContent;
	
	private static ObjectMapper globalObjectMapper = new ObjectMapper();
	
	private static Gson globalGson = new Gson();
	
	private static DslJson<Object>  globalDslJson = new DslJson<>();
	

	private static JsonType<List<Map<String, Object>>> globalAvajeUsersType 
						= Jsonb.builder().build().type(Object.class).map().list();
	
	static {
		try {
			File jsonFile = new File(jsonFilePath);
			jsonFileContent = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8);
			System.out.println("json file content length:"+jsonFileContent.length());
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Jackson test.
	 *
	 * @return the string
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	@Benchmark
	public static String jacksonTest() throws IOException {
		ObjectMapper obm = new ObjectMapper();
		List<Map<?,?>> users= obm.readValue(jsonFileContent,  new TypeReference<List<Map<?,?>>>() {});
		return 	 obm.writeValueAsString(users);
	}
	
	/**
	 * Jackson test.
	 *
	 * @return the string
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	@Benchmark
	public static String jacksonSingletonTest() throws IOException {
		List<Map<?,?>> users= globalObjectMapper.readValue(jsonFileContent,  new TypeReference<List<Map<?,?>>>() {});
		return 	 globalObjectMapper.writeValueAsString(users);
	}
	
	/**
	 * Gson test.
	 *
	 * @return the string
	 */
	@Benchmark
	public static String gsonTest() {
		Gson gson = new Gson();
		List<Map<?,?>> users3 = gson.fromJson(jsonFileContent, new TypeToken<List<Map<?,?>>>(){}); 
		return gson.toJson(users3);
	}
	/**
	 * Gson test.
	 *
	 * @return the string
	 */
	@Benchmark
	public static String gsonSingletonTest() {
		List<Map<?,?>> users3 = globalGson.fromJson(jsonFileContent, new TypeToken<List<Map<?,?>>>(){}); 
		return globalGson.toJson(users3);
	}
	
	/**
	 * jodd json test.
	 *
	 * @return the string
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	@Benchmark
	public static String joddTest() {
		final JsonParser jsonParser = JsonParser.create();
		jsonParser.strictTypes(false);
		List<Map> users2 =  jsonParser.parseAsList(jsonFileContent, Map.class);
		
		final JsonSerializer jsonSerializer = JsonSerializer.create().excludeNulls(false).deep(true);
			
		return jsonSerializer.serialize(users2);
	}
	
	/**
	 * Fastjson 2 test.
	 *
	 * @return the string
	 */
	@Benchmark
	public static String fastjson2Test() {
		List<Map<?,?>> users4= com.alibaba.fastjson2.JSON.parseArray(jsonFileContent, Map.class);
		return com.alibaba.fastjson2.JSON.toJSONString(users4);
	}
	
	/**
	 * Json in java (formerly json-lib) test.
	 */
	@Benchmark
	public static String jsonInJavaTest() {
		
		JSONArray jsonArray = new JSONArray(jsonFileContent);
		return jsonArray.toString();
	}
	
	@Benchmark
	public static String avajeTest() throws IOException {
		Jsonb jsonb = Jsonb.builder().build();

		var usersType = jsonb.type(Map.class).list();
		var users = usersType.fromJson(jsonFileContent);
		return usersType.toJson(users);
	}
	
	@Benchmark
	public static String avajeSingletonTest() {
		
		var users = globalAvajeUsersType.fromJson(jsonFileContent);
		return globalAvajeUsersType.toJson(users);
	}
	@Benchmark
	public static String dslTest() throws IOException {
		DslJson<Object>  dslJson = new DslJson<>();
		try (
			var jsonInput =new ByteArrayInputStream(jsonFileContent.getBytes(StandardCharsets.UTF_8));
			var jsonOutput = new ByteArrayOutputStream()) {
			
			List<Map> users = dslJson.deserializeList(Map.class, new ByteArrayInputStream(jsonFileContent.getBytes()));
			dslJson.serialize(users, jsonOutput);
			return jsonOutput.toString(StandardCharsets.UTF_8);
		}
	}
	@Benchmark
	public static String dslSingletonTest() throws IOException {
		try (
				var jsonInput =new ByteArrayInputStream(jsonFileContent.getBytes(StandardCharsets.UTF_8));
				var jsonOutput = new ByteArrayOutputStream()) {
			
			List<Map> users = globalDslJson.deserializeList(Map.class, new ByteArrayInputStream(jsonFileContent.getBytes()));
			globalDslJson.serialize(users, jsonOutput);
			return jsonOutput.toString(StandardCharsets.UTF_8);
		}
	}
	
	private static void saveResult(Collection<RunResult> results) {
		MongoUtils.getDatabase(JSON_UTILS_DATABASE).getCollection(BENCHMARK_COLLECTION)
			.insertMany(results.stream().map(r->{
				var benchmarkResult = r.getPrimaryResult();
				return new Document("benchmarkName", r.getParams().getBenchmark())
						.append("mode", r.getParams().getMode().name())
						.append("cnt", r.getParams().getMeasurement().getCount())
						.append("cnt", r.getParams().getMeasurement().getCount())
						.append("score", benchmarkResult.getScore())
						.append("error", benchmarkResult.getScoreError())
						.append("units",  benchmarkResult.getScoreUnit())
						.append("updatedTime", new Date());
			}).toList());
	}					

	 /**
 	 * The main method.
 	 *
 	 * @param args the arguments
 	 * @throws RunnerException the runner exception
 	 */
 	public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
            .include(JSONUtilsBenchmark.class.getSimpleName())
            .build();
        Collection<RunResult> result = new Runner(opt).run();
        saveResult(result);
    }
}
